# Python file for Google Desks
# Created by Ayesha Bose, Nico Rakover, and Dylan Tittel
# Last updated: June 15, 2011


# Import statements
import os
os.environ['DJANGO_SETTINGS_MODULE'] = 'settings'

from google.appengine.dist import use_library
use_library('django', '1.2')

from google.appengine.ext import db
from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.ext.webapp.util import run_wsgi_app

import logging
import pickle
from cStringIO import StringIO

import re

# Class to define a property for dictionaries, used in Desk to create desk_space to store user_names and their open documents
# Original source at http://code.google.com/p/googleappengine/issues/detail?id=1106
class DictProperty(db.Property):
  data_type = dict

  def get_value_for_datastore(self, model_instance):
    value = super(DictProperty, self).get_value_for_datastore(model_instance)
    return db.Blob(pickle.dumps(value))

  def make_value_from_datastore(self, value):
    if value is None:
      return dict()
    return pickle.loads(value)

  def default_value(self):
    if self.default is None:
      return dict()
    else:
      return super(DictProperty, self).default_value().copy()

  def validate(self, value):
    if not isinstance(value, dict):
      raise db.BadValueError('Property %s needs to be convertible '
                             'to a dict instance (%s) of class dict' % (self.name, value))
    return super(DictProperty, self).validate(value)

  def empty(self, value):
    return value is None    


# Class to define a desk. This object is created by a user on the home page and is meant to be temporary. 
class Desk(db.Model):
  name = db.StringProperty(required=True) # Name of desk, in the format: creator-name
  desk_space = DictProperty(required=True) # Dictionary where keys = user_names, values = list of open documents at this desk for each user

# Class to define a user. This is created at the register page.
class User(db.Model):
  display_name = db.StringProperty(required=True) # Display name, not used yet
  user_name = db.StringProperty(required=True) # User name, in the format of an email address
  password = db.StringProperty(required=True) # Password
  docs_list = db.StringListProperty(required=True) # List of document names that this user owns

# Class to define a document. This is created when a user saves a document.
class Document(db.Model):
  name = db.StringProperty(required=True) # Name of document
  owner = db.StringProperty(required=True) # User_name of owner of document
  text = db.StringProperty(required=False) # Text of document
  can_edit = db.BooleanProperty(required=True) # Editable property, default allows document to be edited by peers
  visible = db.BooleanProperty(required=True) # Visibility property, default allows document to be seen by peers

# Class to handle login page
class LoginHandler(webapp.RequestHandler):
  def get(self):
    template_values = {'message': ''}
    path = os.path.join(os.path.dirname(__file__), 'index.html')
    self.response.out.write(template.render(path, template_values)) # Displays login page with no message

  def post(self):
    template_values = {} # Resetting template values

    for desk in Desk.all():
      if len(desk.desk_space) == 0:
        desk.delete()

    # Getting input values
    login = self.request.get('login')
    register = self.request.get('register')
    user_name = self.request.get('user_name')
    password = self.request.get('password')

    # Check to see if user entered in all information and wants to login
    if login and user_name and password:
      query = User.all()
      account = query.filter('user_name = ', user_name)
      # Check to see if user_name was found in database
      if account.count()==1:
        # Check to see if password matches
        if account[0].password == password:
          template_values = {'user_name' : account[0].user_name, 'docs_list' : account[0].docs_list, 'desk_name' : '', 'message' : ''}
          path = os.path.join(os.path.dirname(__file__), 'home.html')
          self.response.out.write(template.render(path, template_values)) # Re-directs to home page, passes user_name and docs_list 
        else:
          template_values['message'] = 'Invalid login'
          path = os.path.join(os.path.dirname(__file__), 'index.html')
          self.response.out.write(template.render(path, template_values)) # Displays login page with message 'Invalid login'
      else:
        template_values['message'] = 'Username not found'
        path = os.path.join(os.path.dirname(__file__), 'index.html')
        self.response.out.write(template.render(path, template_values)) # Displays login page with message 'Username not found'

    # Check to see if user wants to register
    elif register:
      path = os.path.join(os.path.dirname(__file__), 'registration.html')
      self.response.out.write(template.render(path, template_values)) # Re-directs to registration page
    else:
      template_values['message'] = 'Invalid login'
      path = os.path.join(os.path.dirname(__file__), 'index.html')
      self.response.out.write(template.render(path, template_values)) # Displays login page with message 'Invalid login'
    
# Class to handle registration page
class RegistrationHandler(webapp.RequestHandler):
  def post(self):
    template_values= {}
    
    # Getting input values
    user_name = self.request.get('user_name')
    password = self.request.get('password')
    confirm = self.request.get('confirm')
    display_name = self.request.get('display_name')

    # Check to see if user entered in all information 
    if user_name and password and display_name and confirm:
      query = User.all()
      account = query.filter('user_name = ', user_name)
      # Check to see if user already exists
      if account.count()==1:
        template_values = {'message' : 'Proposed Username is already in use'}
        path = os.path.join(os.path.dirname(__file__), 'registration.html')
        self.response.out.write(template.render(path, template_values)) # Displays registration page with message 'Proposed Username...'
      else:
        if '/' in user_name:
          template_values = {'message' : 'Usernames cannot have "/"'}
          path = os.path.join(os.path.dirname(__file__), 'registration.html')
          self.response.out.write(template.render(path, template_values)) # Displays registration page with message 'Usernames.."
        else:
          # Check passwords
          if confirm == password:
            new_user = User(display_name=display_name, user_name=user_name, password=password, docs_list=[])
            new_user.put() # Adds new user to database

            template_values['message'] = ''
            path = os.path.join(os.path.dirname(__file__), 'index.html')
            self.response.out.write(template.render(path, template_values)) # Re-directs to home page
          else:
            template_values = {'message' : "Passwords don't match"}
            path = os.path.join(os.path.dirname(__file__), 'registration.html')
            self.response.out.write(template.render(path, template_values)) # Displays registration page with message 'Passwords don't match"
    else:
      template_values = {'message' : "You didn't enter in anything!"}
      path = os.path.join(os.path.dirname(__file__), 'registration.html')
      self.response.out.write(template.render(path, template_values)) # Displays registration page with message 'You didn't enter..."

# Class to handle home page   
class HomeHandler(webapp.RequestHandler):  
  def get(self):
    user_name = self.request.get('user_name')
    template_values = {'user_name' : user_name, 'docs_list' : self.request.get('docs_list'), 'message' : ''}
    path = os.path.join(os.path.dirname(__file__), 'home.html')
    self.response.out.write(template.render(path, template_values)) # Displays home page

  def post(self):
    logout = self.request.get('logout')
    user_name = self.request.get('user_name')
    home = self.request.get('home')

    if logout:
      logging.info ("INSIDE LOGOUT!")
      desks = Desk.all()
      for desk in desks:
          if user_name in desk.desk_space:
            logging.info(user_name)
            del desk.desk_space[user_name]
            desk.put()
      template_values = {'message': ''}
      path = os.path.join(os.path.dirname(__file__), 'index.html')
      self.response.out.write(template.render(path, template_values)) # Displays login page with no message
    if home:
      template_values = {'user_name' : user_name, 'docs_list' : self.request.get('docs_list'), 'message' : ''}
      path = os.path.join(os.path.dirname(__file__), 'home.html')
      self.response.out.write(template.render(path, template_values)) # Displays home page

# Class to handle desk page
class DeskHandler(webapp.RequestHandler):
  def get(self):
    logging.info('Inside desk handler! >>>> GET') # DELETE LATER
    
  def post(self):
    logging.info('Inside desk handler! >>> POST') # DELETE LATER

    # Getting input values
    user_name = self.request.get('user_name')
    desk_name = self.request.get('desk_name')
    create = self.request.get('create')
    join = self.request.get('join')
    save = self.request.get('save')
    close = self.request.get('close')
    user_desk_name = self.request.get('user_desk_name')
    doc_name = self.request.get('doc_name')
    re.sub('[^-.:\\w]', '_', doc_name) # Immediately replaces forbidden characters with underscores (MobWrite limitations)
    doc_text = self.request.get('paper')
    

    # Setting default values for buttons
    doc_to_open = None
    open_doc_button = False
    peer_to_open = None
    open_peer_button = False
    
    docs_list = User.all().filter('user_name = ', user_name)[0].docs_list # Opens documents user owns

    # Handling of clicking in drop down menus to open a document or a peer's open documents
    # Checks to make sure desk has been created
    if desk_name != '':
      desk_space = Desk.all().filter('name = ', desk_name)[0].desk_space # Get desk space associated with this desk
      # Cycles through dictionary in desk
      for k, v in desk_space.items():
        # Checks to make sure that the key is a peer
        if k != user_name:
          # If a peer was clicked...
          if self.request.get(k):
            peer_to_open = k # Record name of peer to open
            open_peer_button = True 
    # Checks to make sure documents for the user exist
    if len(docs_list) != 0:
      # Cycles through each document in the list
      for doc in docs_list:
        # If a document was clicked...
        if self.request.get(doc):
          doc_to_open = doc # Record name of doc to open
          open_doc_button = True

    # Checks to see if user is trying to join or create a desk
    if join or create:
      self.home_post(user_name, user_desk_name, join, create) # Calls outside function on home page

    # Checks to see if other buttons are pressed on desk page
    elif save or open_doc_button or close or open_peer_button:
      self.desk_post(user_name, desk_name, doc_name, doc_text, save, doc_to_open, open_doc_button, close, peer_to_open, open_peer_button) # Calls outside function on desk page

  # Function that either creates or joins a desk 
  def home_post(self, user_name, user_desk_name, join, create):
    logging.info('Inside >>>>>>>>>>  HOME_POST') # DELETE LATER
    # Checks to make sure a desk name was entered
    if user_desk_name!='':
      # Checks to see if user wants to create a desk
      if create:
        self.CreateDesk(user_name, user_desk_name) # Calls outside function to create a desk
      # Checks to see if user wants to join a desk
      elif join:
        self.JoinDesk(user_desk_name, user_name) # Calls outside function to join a desk
    else:
      template_values = {'user_name' : user_name, 'docs_list' : self.request.get('docs_list'), 'message' : "You didn't enter in anything!"}
      path = os.path.join(os.path.dirname(__file__), 'home.html')
      self.response.out.write(template.render(path, template_values)) # Displays home page with message 'You didn't enter...'

  # Function that deals with desk page
  def desk_post(self, user_name, desk_name, doc_name, doc_text, save, doc_to_open, open_doc_button, close, peer_to_open, open_peer_button):
    logging.info('Inside >>>>>>>>>>  DESK_POST') # DELETE LATER
    logging.info('Doc_to_open') # DELETE LATER
    logging.info(doc_to_open) # DELETE LATER
    # Checks to see if user wants to save a document
    if save:
      # Checks to make sure a doc_name was entered
      if doc_name != '':
        self.SaveDoc(user_name, desk_name, doc_name, doc_text) # Calls outside function to save a doc
      else:
        logging.info(desk_name)
        self.DisplayDesk(desk_name, user_name, 'Document name required') # Calls outside function to display desk with a message 'Document name required'
    # Checks to see if user wants to open a document
    if open_doc_button:
      desk = Desk.all().filter('name = ', desk_name)[0] # Gets desk
      # Checks to make sure document is not already open
      if not doc_to_open in desk.desk_space[user_name]:
        desk.desk_space[user_name].append(doc_to_open)
        desk.put() # Adds document to list of open docs associated with this user
      self.DisplayDesk(desk_name, user_name, '') # Calls outside function to display desk
    # Checks to see if user wants to close a document
    if close:
      desk = Desk.all().filter('name = ', desk_name)[0]
      desk.desk_space[user_name].remove(doc_name)
      desk.put() # Removes document from list of open docs associated with this user
      self.DisplayDesk(desk_name, user_name, '') 
    # Checks to see if user wants to open a peer's open documents
    if open_peer_button:
      desk = Desk.all().filter('name = ', desk_name)[0]
      peer_docs = desk.desk_space[peer_to_open] # Gets list of open documents for this peer
      # Cycles through list of open docs
      for peer_doc in peer_docs:
        if Document.all().filter('name = ', peer_doc).count() != 0:
          if Document.all().filter('name = ', peer_doc)[0].visible == True:
            if Document.all().filter('name = ', peer_doc)[0].can_edit == False:
              desk.desk_space[user_name].append('.'.join([peer_doc,user_name]))
              desk.put() # Adds peer's doc to user's doc list
            # Checks to make sure peer's doc isn't already in user's docs
            elif not peer_doc in desk.desk_space[user_name]:
              desk.desk_space[user_name].append(peer_doc)
              desk.put() # Adds peer's doc to user's doc list
      self.DisplayDesk(desk_name, user_name, '') 
  
  # Function that creates desk 
  def CreateDesk(self, user_name, user_desk_name):
    if '/' in user_desk_name:
      template_values = {'user_name' : user_name, 'desk_name' : '', 'desk_space' : {}, 'message' : 'Desk names cannot have "/"'}
      path = os.path.join(os.path.dirname(__file__), 'home.html')
      self.response.out.write(template.render(path, template_values)) # Displays registration page with message 'Desk names.."
    else:
      desk_name = '-'.join([self.request.get('user_name'), user_desk_name]) # Formats desk name into creator-desk name
      if Desk.all().filter('name = ', desk_name).count() == 0 :
        new_desk = Desk(name=desk_name, desk_space = {user_name : []})
        new_desk.put() # Adds new desk to database
        self.DisplayDesk(desk_name, user_name, '')
      else:
        template_values = {'user_name' : user_name, 'desk_name' : '', 'desk_space' : {}, 'message' : 'Desk already exists'}
        path = os.path.join(os.path.dirname(__file__), 'home.html')
        self.response.out.write(template.render(path, template_values)) # Displays home page with message 'Desk already exists'


  # Function that allows user to join desk
  def JoinDesk(self, desk_name, user_name):
    query = Desk.all()
    desk = query.filter('name = ', desk_name)
    # Checks to see if desk exists
    if desk.count()==1:
      self.DisplayDesk(desk_name, user_name, '')
    else:
      template_values = {'user_name' : user_name, 'desk_name' : '', 'desk_space' : {}, 'message' : 'Invalid Desk Name'}
      path = os.path.join(os.path.dirname(__file__), 'home.html')
      self.response.out.write(template.render(path, template_values)) # Displays home page with message 'Invalid Desk Name'
  
  # Function to display desk
  def DisplayDesk(self, desk_name, user_name, message):
    docs = {}
    logging.info('Inside >>>>>>>>>>  DISPLAYDESK') # DELETE LATER
    desk = Desk.all().filter('name = ', desk_name)[0]
    my_docs = User.all().filter('user_name = ', user_name)[0].docs_list # Gets a list of all documents user has
    desk_space = desk.desk_space
    # Checks to see if user is at the desk
    if not desk_space.has_key(user_name):
      desk_space[user_name] = []
      desk.desk_space = desk_space
      desk.put() # Adds user to desk
    peers = desk_space.keys()
    peers.remove(user_name) # Gets a list of peers
    my_open_docs = desk.desk_space[user_name] 
    my_open_docs.reverse() # Gets a list of user's open docs in order of date created, most recent at the top
    # Cycles through my open documents
    for i in my_open_docs:
      l = i.partition(' ')
      k = l[0]
      # Checks to make sure document exists
      if Document.all().filter('name = ', k).count() != 0:
        docs[i] = Document.all().filter('name = ', k)[0].text # Adds doc text to array
    template_values = {'user_name' : user_name, 'my_docs' : my_docs, 'my_open_docs' : my_open_docs,'desk_name' : desk_name, 'desk_space' : desk_space,'peers' : peers, 'docs' : docs, 'message' : message}
    path = os.path.join(os.path.dirname(__file__), 'desk.html')
    self.response.out.write(template.render(path, template_values)) # Displays desk page with open documents

  # Function to save a document
  def SaveDoc(self,user_name, desk_name, name, doc_text, editable=False):
    logging.info('Inside >>>>>>>>>>  SAVEDOC') # DELETE LATER
    if self.request.get('visibility') != '':
      vis = True
    else:
      vis = False
    if self.request.get('sharing') != '':
      edit = True
    else:
      edit = False
    
    doc = Document.all().filter('name = ', name).filter('owner = ', user_name) # 
    # Checks to see if document exists already
    if doc.count()==0:
      new_doc = Document(name=name,owner=user_name, text=doc_text, can_edit=edit, visible=vis)
      new_doc.put()
      user = User.all().filter('user_name = ', user_name)[0]
      user.docs_list.append(name)
      user.put()
      desk = Desk.all().filter('name = ', desk_name)[0]
      desk.desk_space[user_name].append(name)
      desk.put() # Saves a new document
    else:
      doc[0].text = doc_text
      doc[0].put() # Updates existing document
    self.DisplayDesk(desk_name, user_name, '')


# Boiler-plate
def main():
  application = webapp.WSGIApplication([('/', LoginHandler),
                                        ('/registration.*', RegistrationHandler),
                                        ('/home.*', HomeHandler),
                                        ('/desk.*', DeskHandler)]
                                       , debug=True)
  run_wsgi_app(application)

if __name__ == '__main__':
  main()
